from datetime import datetime, timedelta
from functools import partial
from typing import Tuple, Dict

import pandas as pd
from PyQt5.QtCore import QThread, pyqtSignal, Qt
from PyQt5.QtWidgets import QCompleter

from vnsumscope.trader.constant import Interval, Exchange
from vnsumscope.trader.database import DB_TZ
from vnsumscope.trader.engine import MainEngine, EventEngine
from vnsumscope.trader.ui import QtWidgets, QtCore
from ..engine import APP_NAME, ManagerEngine


class ManagerWidget(QtWidgets.QWidget):
    """"""

    def __init__(self, main_engine: MainEngine, event_engine: EventEngine):
        """"""
        super().__init__()

        self.engine: ManagerEngine = main_engine.get_engine(APP_NAME)

        self.tree_items: Dict[Tuple, QtWidgets.QTreeWidgetItem] = {}

        self.init_ui()

    def init_ui(self) -> None:
        """"""
        self.setWindowTitle("数据管理")

        self.init_tree()
        self.init_table()
        self.init_child()

        refresh_button = QtWidgets.QPushButton("刷新")
        refresh_button.clicked.connect(self.refresh_tree)

        import_button = QtWidgets.QPushButton("导入数据")
        import_button.clicked.connect(self.import_data)

        update_button = QtWidgets.QPushButton("更新数据")
        update_button.clicked.connect(self.update_data)
        update_button.setEnabled(False)

        download_button = QtWidgets.QPushButton("下载数据")
        download_button.clicked.connect(self.download_data)
        download_button.setEnabled(False)

        hbox1 = QtWidgets.QHBoxLayout()
        hbox1.addWidget(refresh_button)
        hbox1.addStretch()
        hbox1.addWidget(import_button)
        hbox1.addWidget(update_button)
        hbox1.addWidget(download_button)

        hbox2 = QtWidgets.QHBoxLayout()
        hbox2.addWidget(self.tree)
        hbox2.addWidget(self.table)

        vbox = QtWidgets.QVBoxLayout()
        vbox.addLayout(hbox1)
        vbox.addLayout(hbox2)

        self.setLayout(vbox)

    def init_tree(self) -> None:
        """"""
        labels = [
            "数据",
            "本地代码",
            "代码",
            "交易所",
            "数据量",
            "开始时间",
            "结束时间",
            "",
            "",
            ""
        ]

        self.tree = QtWidgets.QTreeWidget()
        self.tree.setColumnCount(len(labels))
        self.tree.setHeaderLabels(labels)

    def init_child(self) -> None:
        """"""
        self.minute_child = QtWidgets.QTreeWidgetItem()
        self.minute_child.setText(0, "分钟线")
        self.tree.addTopLevelItem(self.minute_child)

        self.hour_child = QtWidgets.QTreeWidgetItem(self.tree)
        self.hour_child.setText(0, "小时线")
        self.tree.addTopLevelItem(self.hour_child)

        self.daily_child = QtWidgets.QTreeWidgetItem(self.tree)
        self.daily_child.setText(0, "日线")
        self.tree.addTopLevelItem(self.daily_child)

    def init_table(self) -> None:
        """"""
        labels = [
            "时间",
            "开盘价",
            "最高价",
            "最低价",
            "收盘价",
            "成交量",
            "持仓量"
        ]

        self.table = QtWidgets.QTableWidget()
        self.table.setColumnCount(len(labels))
        self.table.setHorizontalHeaderLabels(labels)
        self.table.verticalHeader().setVisible(False)
        self.table.horizontalHeader().setSectionResizeMode(
            QtWidgets.QHeaderView.ResizeToContents
        )

    def clear_tree(self) -> None:
        """"""
        for key, item in self.tree_items.items():
            interval = key[2]

            if interval == Interval.MINUTE.value:
                self.minute_child.removeChild(item)
            elif interval == Interval.HOUR.value:
                self.hour_child.removeChild(item)
            else:
                self.daily_child.removeChild(item)

        self.tree_items.clear()

    def refresh_tree(self) -> None:
        """"""
        self.clear_tree()

        overviews = self.engine.get_bar_overview()

        for overview in overviews:
            key = (overview.symbol, overview.exchange, overview.interval)
            item = self.tree_items.get(key, None)

            if not item:
                item = QtWidgets.QTreeWidgetItem()
                self.tree_items[key] = item

                item.setText(1, f"{overview.symbol}.{overview.exchange.value}")
                item.setText(2, overview.symbol)
                item.setText(3, overview.exchange.value)

                if overview.interval == Interval.MINUTE:
                    self.minute_child.addChild(item)
                elif overview.interval == Interval.HOUR:
                    self.hour_child.addChild(item)
                else:
                    self.daily_child.addChild(item)

                output_button = QtWidgets.QPushButton("导出")
                output_func = partial(
                    self.output_data,
                    overview.symbol,
                    overview.exchange,
                    overview.interval,
                    overview.start,
                    overview.end
                )
                output_button.clicked.connect(output_func)

                show_button = QtWidgets.QPushButton("查看")
                show_func = partial(
                    self.show_data,
                    overview.symbol,
                    overview.exchange,
                    overview.interval,
                    overview.start,
                    overview.end
                )
                show_button.clicked.connect(show_func)

                delete_button = QtWidgets.QPushButton("删除")
                delete_func = partial(
                    self.delete_data,
                    overview.symbol,
                    overview.exchange,
                    overview.interval
                )
                delete_button.clicked.connect(delete_func)

                self.tree.setItemWidget(item, 7, show_button)
                self.tree.setItemWidget(item, 8, output_button)
                self.tree.setItemWidget(item, 9, delete_button)

            item.setText(4, str(overview.count))
            item.setText(5, overview.start.strftime("%Y-%m-%d %H:%M:%S"))
            item.setText(6, overview.end.strftime("%Y-%m-%d %H:%M:%S"))

        self.minute_child.setExpanded(True)
        self.hour_child.setExpanded(True)
        self.daily_child.setExpanded(True)

    def import_data(self) -> None:
        """"""
        self.dialog = ImportDialog()
        n = self.dialog.exec_()
        if n != self.dialog.Accepted:
            return
        file_path = self.dialog.file_edit.text()
        symbol = self.dialog.symbol_edit.text()
        exchange = self.dialog.exchange_combo.currentData()
        interval = self.dialog.interval_combo.currentData()
        symbol_head = self.dialog.symbol_edit_head.text()
        datetime_head = self.dialog.datetime_edit.text()
        open_head = self.dialog.open_edit.text()
        low_head = self.dialog.low_edit.text()
        high_head = self.dialog.high_edit.text()
        close_head = self.dialog.close_edit.text()
        volume_head = self.dialog.volume_edit.text()
        open_interest_head = self.dialog.open_interest_edit.text()
        datetime_format = self.dialog.format_edit.text()
        progressbar = self.dialog.progressbar
        msgbox_info = self.dialog.qmessagebox_info

        self.readcsv_thread = ReadCsvThread(engine=self.engine, file_path=file_path, exchange=exchange, interval=interval, symbol_head=symbol_head, datetime_head=datetime_head, open_head=open_head, low_head=low_head,
                                  high_head=high_head, close_head=close_head,
                                  volume_head=volume_head, open_interest_head=open_interest_head, datetime_format=datetime_format, progressbar=progressbar, msgbox_info=msgbox_info)
        self.readcsv_thread.start()
        self.readcsv_thread.db_signal.connect(self.csv_to_sqlite)  # 在UI线程中进行信号和槽的连接

    def csv_to_sqlite(self, count: str) -> None:
        ''''''
        self.dialog.progressbar.setValue(float(count))

    def output_data(
            self,
            symbol: str,
            exchange: Exchange,
            interval: Interval,
            start: datetime,
            end: datetime
    ) -> None:
        """"""
        # Get output date range
        dialog = DateRangeDialog(start, end)
        n = dialog.exec_()
        if n != dialog.Accepted:
            return
        start, end = dialog.get_date_range()

        # Get output file path
        path, _ = QtWidgets.QFileDialog.getSaveFileName(
            self,
            "导出数据",
            "",
            "CSV(*.csv)"
        )
        if not path:
            return

        result = self.engine.output_data_to_csv(
            path,
            symbol,
            exchange,
            interval,
            start,
            end
        )

        if not result:
            QtWidgets.QMessageBox.warning(
                self,
                "导出失败！",
                "该文件已在其他程序中打开，请关闭相关程序后再尝试导出数据。"
            )

    def show_data(
            self,
            symbol: str,
            exchange: Exchange,
            interval: Interval,
            start: datetime,
            end: datetime
    ) -> None:
        """"""
        # Get output date range
        dialog = DateRangeDialog(start, end)
        n = dialog.exec_()
        if n != dialog.Accepted:
            return
        start, end = dialog.get_date_range()

        bars = self.engine.load_bar_data(
            symbol,
            exchange,
            interval,
            start,
            end
        )

        self.table.setRowCount(0)
        self.table.setRowCount(len(bars))

        for row, bar in enumerate(bars):
            self.table.setItem(row, 0, DataCell(bar.datetime.strftime("%Y-%m-%d %H:%M:%S")))
            self.table.setItem(row, 1, DataCell(str(bar.open_price)))
            self.table.setItem(row, 2, DataCell(str(bar.high_price)))
            self.table.setItem(row, 3, DataCell(str(bar.low_price)))
            self.table.setItem(row, 4, DataCell(str(bar.close_price)))
            self.table.setItem(row, 5, DataCell(str(bar.volume)))
            self.table.setItem(row, 6, DataCell(str(bar.open_interest)))

    def delete_data(
            self,
            symbol: str,
            exchange: Exchange,
            interval: Interval
    ) -> None:
        """"""
        n = QtWidgets.QMessageBox.warning(
            self,
            "删除确认",
            f"请确认是否要删除{symbol} {exchange.value} {interval.value}的全部数据",
            QtWidgets.QMessageBox.Ok,
            QtWidgets.QMessageBox.Cancel
        )

        if n == QtWidgets.QMessageBox.Cancel:
            return

        count = self.engine.delete_bar_data(
            symbol,
            exchange,
            interval
        )

        QtWidgets.QMessageBox.information(
            self,
            "删除成功",
            f"已删除{symbol} {exchange.value} {interval.value}共计{count}条数据",
            QtWidgets.QMessageBox.Ok
        )

    def update_data(self) -> None:
        """"""
        data = self.engine.get_bar_data_available()
        total = len(data)
        count = 0

        dialog = QtWidgets.QProgressDialog(
            "历史数据更新中",
            "取消",
            0,
            100
        )
        dialog.setWindowTitle("更新进度")
        dialog.setWindowModality(QtCore.Qt.WindowModal)
        dialog.setValue(0)

        for d in data:
            if dialog.wasCanceled():
                break

            self.engine.download_bar_data(
                d["symbol"],
                Exchange(d["exchange"]),
                Interval(d["interval"]),
                d["end"]
            )
            count += 1
            progress = int(round(count / total * 100, 0))
            dialog.setValue(progress)

        dialog.close()

    def download_data(self) -> None:
        """"""
        dialog = DownloadDialog(self.engine)
        dialog.exec_()

    def show(self) -> None:
        """"""
        self.showMaximized()


class DataCell(QtWidgets.QTableWidgetItem):
    """"""

    def __init__(self, text: str = ""):
        super().__init__(text)

        self.setTextAlignment(QtCore.Qt.AlignCenter)


class DateRangeDialog(QtWidgets.QDialog):
    """"""

    def __init__(self, start: datetime, end: datetime, parent=None):
        """"""
        super().__init__(parent)

        self.setWindowTitle("选择数据区间")

        self.start_edit = QtWidgets.QDateEdit(
            QtCore.QDate(
                start.year,
                start.month,
                start.day
            )
        )
        self.end_edit = QtWidgets.QDateEdit(
            QtCore.QDate(
                end.year,
                end.month,
                end.day
            )
        )

        button = QtWidgets.QPushButton("确定")
        button.clicked.connect(self.accept)

        form = QtWidgets.QFormLayout()
        form.addRow("开始时间", self.start_edit)
        form.addRow("结束时间", self.end_edit)
        form.addRow(button)

        self.setLayout(form)

    def get_date_range(self) -> Tuple[datetime, datetime]:
        """"""
        start = self.start_edit.dateTime().toPyDateTime()
        end = self.end_edit.dateTime().toPyDateTime() + timedelta(days=1)
        return start, end


class ImportDialog(QtWidgets.QDialog):
    """"""

    def __init__(self, parent=None):
        """"""
        super().__init__()

        self.setWindowTitle("从CSV文件导入数据")
        self.setFixedWidth(600)

        self.setWindowFlags(
            (self.windowFlags() | QtCore.Qt.CustomizeWindowHint)
            & ~QtCore.Qt.WindowMaximizeButtonHint)

        self.file_edit = QtWidgets.QLineEdit()
        self.symbol_edit = QtWidgets.QLineEdit()

        file_button = QtWidgets.QPushButton("选择文件")
        file_button.clicked.connect(self.select_file)

        self.load_button = QtWidgets.QPushButton("确定")
        if not self.file_edit.text():
            self.load_button.setEnabled(False)
        self.load_button.clicked.connect(self.accept_bar)

        # self.file_edit = QtWidgets.QLineEdit()
        # self.symbol_edit = QtWidgets.QLineEdit()

        self.exchange_combo = QtWidgets.QComboBox()
        self.exchange_combo.addItem('')
        for i in Exchange:
            self.exchange_combo.addItem(str(i.name), i)

        self.interval_combo = QtWidgets.QComboBox()
        self.interval_combo.addItem('')
        for i in Interval:
            if i != Interval.TICK:
                self.interval_combo.addItem(str(i.name), i)

        self.symbol_edit_head = QtWidgets.QLineEdit("symbol")
        self.symbol_edit_head.setPlaceholderText('请输入匹配的csv列名，如:symbol')

        self.datetime_edit = QtWidgets.QLineEdit("datetime")
        self.datetime_edit.setPlaceholderText('请输入匹配的csv列名，如:datetime')

        self.open_edit = QtWidgets.QLineEdit("open")
        self.open_edit.setPlaceholderText('请输入匹配的csv列名，如:open')

        self.high_edit = QtWidgets.QLineEdit("high")
        self.high_edit.setPlaceholderText('请输入匹配的csv列名，如:high')

        self.low_edit = QtWidgets.QLineEdit("low")
        self.low_edit.setPlaceholderText('请输入匹配的csv列名，如:low')

        self.close_edit = QtWidgets.QLineEdit("close")
        self.close_edit.setPlaceholderText('请输入匹配的csv列名，如:close')

        self.volume_edit = QtWidgets.QLineEdit("volume")
        self.volume_edit.setPlaceholderText('请输入匹配的csv列名，如:volume')

        self.open_interest_edit = QtWidgets.QLineEdit("open_interest")
        self.open_interest_edit.setPlaceholderText('请输入匹配的csv列名，如:open_interest')

        self.format_edit = QtWidgets.QLineEdit("%Y-%m-%d %H:%M:%S")
        self.open_interest_edit.setPlaceholderText('请输入csv中时间列的格式，如:%Y-%m-%d %H:%M:%S')

        info_label = QtWidgets.QLabel("合约信息")
        info_label.setAlignment(QtCore.Qt.AlignCenter)

        head_label = QtWidgets.QLabel("表头信息")
        head_label.setAlignment(QtCore.Qt.AlignCenter)

        format_label = QtWidgets.QLabel("格式信息")
        format_label.setAlignment(QtCore.Qt.AlignCenter)

        form = QtWidgets.QFormLayout()
        form.addRow(file_button, self.file_edit)
        form.addRow(QtWidgets.QLabel())
        form.addRow(info_label)
        form.addRow("交易所", self.exchange_combo)
        form.addRow("周期", self.interval_combo)
        form.addRow(QtWidgets.QLabel())
        form.addRow(head_label)
        form.addRow("品种代码", self.symbol_edit_head)
        form.addRow("时间戳", self.datetime_edit)
        form.addRow("开盘价", self.open_edit)
        form.addRow("最高价", self.high_edit)
        form.addRow("最低价", self.low_edit)
        form.addRow("收盘价", self.close_edit)
        form.addRow("成交量", self.volume_edit)
        form.addRow("持仓量", self.open_interest_edit)
        form.addRow(QtWidgets.QLabel())
        form.addRow(format_label)
        form.addRow("时间格式", self.format_edit)
        form.addRow(QtWidgets.QLabel())
        form.addRow(self.load_button)
        self.setLayout(form)

    def accept_bar(self):
        '''数据导入进度条'''
        exchange = self.exchange_combo.currentData()
        interval = self.interval_combo.currentData()
        if not exchange:
            QtWidgets.QMessageBox.information(self, '异常捕获', '交易所代码不能为空', QtWidgets.QMessageBox.Yes)
        elif not interval:
            QtWidgets.QMessageBox.information(self, '异常捕获', '周期不能为空', QtWidgets.QMessageBox.Yes)
        else:
            self.accept()
            self.progressbar = QtWidgets.QProgressDialog(self)
            # 进度条大小
            min_width = max(QtWidgets.QApplication.desktop().width()/8, 375)
            self.progressbar.setMinimumWidth(min_width)
            self.progressbar.setRange(0, 100)
            self.progressbar.setLabelText('数据导入中...')
            self.progressbar.setCancelButtonText(None)
            self.progressbar.show()


    def init_lineedit(self, file_path: str):
        '''输入框提示'''
        df_k = pd.read_csv(file_path,nrows=0)
        columns_list = df_k.columns.tolist()
        self.completer = QCompleter(columns_list)
        self.completer.setFilterMode(Qt.MatchContains)
        self.completer.setCompletionMode(QCompleter.PopupCompletion)
        self.symbol_edit_head.setCompleter(self.completer)
        self.datetime_edit.setCompleter(self.completer)
        self.open_edit.setCompleter(self.completer)
        self.high_edit.setCompleter(self.completer)
        self.low_edit.setCompleter(self.completer)
        self.close_edit.setCompleter(self.completer)
        self.volume_edit.setCompleter(self.completer)
        self.open_interest_edit.setCompleter(self.completer)

    def qmessagebox_info(self, msg: str):
        # 提示框
        QtWidgets.QMessageBox.information(self, '异常捕获', msg, QtWidgets.QMessageBox.Yes)

    def select_file(self, ):
        """"""
        result: str = QtWidgets.QFileDialog.getOpenFileName(
            self, filter="CSV (*.csv)")
        filename = result[0]
        if filename:
            self.file_edit.setText(filename)
            self.init_lineedit(self.file_edit.text())
            self.load_button.setEnabled(True)


class DownloadDialog(QtWidgets.QDialog):
    """"""

    def __init__(self, engine: ManagerEngine, parent=None):
        """"""
        super().__init__()

        self.engine = engine

        self.setWindowTitle("下载历史数据")
        self.setFixedWidth(300)

        self.setWindowFlags(
            (self.windowFlags() | QtCore.Qt.CustomizeWindowHint)
            & ~QtCore.Qt.WindowMaximizeButtonHint)

        self.symbol_edit = QtWidgets.QLineEdit()

        self.exchange_combo = QtWidgets.QComboBox()
        for i in Exchange:
            self.exchange_combo.addItem(str(i.name), i)

        self.interval_combo = QtWidgets.QComboBox()
        for i in Interval:
            self.interval_combo.addItem(str(i.name), i)

        end_dt = datetime.now()
        start_dt = end_dt - timedelta(days=3 * 365)

        self.start_date_edit = QtWidgets.QDateEdit(
            QtCore.QDate(
                start_dt.year,
                start_dt.month,
                start_dt.day
            )
        )

        button = QtWidgets.QPushButton("下载")
        button.clicked.connect(self.download)

        form = QtWidgets.QFormLayout()
        form.addRow("代码", self.symbol_edit)
        form.addRow("交易所", self.exchange_combo)
        form.addRow("周期", self.interval_combo)
        form.addRow("开始日期", self.start_date_edit)
        form.addRow(button)

        self.setLayout(form)

    def download(self):
        """"""
        symbol = self.symbol_edit.text()
        exchange = Exchange(self.exchange_combo.currentData())
        interval = Interval(self.interval_combo.currentData())

        start_date = self.start_date_edit.date()
        start = datetime(start_date.year(), start_date.month(), start_date.day())
        start = DB_TZ.localize(start)

        if interval == Interval.TICK:
            count = self.engine.download_tick_data(symbol, exchange, start)
        else:
            count = self.engine.download_bar_data(symbol, exchange, interval, start)
        QtWidgets.QMessageBox.information(self, "下载结束", f"下载总数据量：{count}条")


class ReadCsvThread(QThread):
    db_signal = pyqtSignal(str)

    def __init__(self, engine=None, file_path=None, exchange=None, interval=None, symbol_head=None, datetime_head=None, open_head=None, low_head=None, high_head=None, close_head=None,
                 volume_head=None, open_interest_head=None, datetime_format=None, progressbar=None, msgbox_info=None):
        super(ReadCsvThread, self).__init__()
        self.engine = engine
        self.file_path = file_path
        self.exchange = exchange
        self.interval = interval
        self.symbol_head = symbol_head
        self.datetime_head = datetime_head
        self.open_head = open_head
        self.low_head = low_head
        self.high_head = high_head
        self.close_head = close_head
        self.volume_head = volume_head
        self.open_interest_head = open_interest_head
        self.datetime_format = datetime_format
        self.progressbar = progressbar
        self.msgbox_info = msgbox_info

    def run(self):
        df_k = pd.read_csv(self.file_path)
        total_count = df_k.shape[0] - 1
        if total_count == 0:
            self.progressbar.close()
            msg = '您导入的文件内容是空，请重新导入'
            self.msgbox_info(msg)
            return
        columns_list = df_k.columns.tolist()
        self_columns_list = [self.symbol_head, self.open_head, self.open_interest_head, self.low_head, self.high_head, self.volume_head, self.close_head, self.datetime_head]
        temp_list = []
        for cloumn_name in self_columns_list:
            if cloumn_name not in columns_list:
                temp_list.append(cloumn_name)
        if temp_list:
            cloumn_name_str = ','.join(temp_list)
            self.progressbar.close()
            msg = f"csv文件中未找到您输入的{cloumn_name_str}列"
            self.msgbox_info(msg)
            return
        try:
            dt = datetime.strptime(df_k[self.datetime_head][0], self.datetime_format)
        except ValueError:
            self.progressbar.close()
            msg = '您输入的时间格式不正确'
            self.msgbox_info(msg)
            return
        symbol_list = df_k[self.symbol_head].apply(lambda x: str(x)).unique()
        progressbar_sum = 0
        for symbol in symbol_list:
            start, end, count = self.engine.import_data_from_csv(
                self.file_path,
                symbol,
                self.exchange,
                self.interval,
                self.datetime_head,
                self.open_head,
                self.high_head,
                self.low_head,
                self.close_head,
                self.volume_head,
                self.open_interest_head,
                self.datetime_format,
                self.symbol_head
            )
            # 给进度条发信号
            progressbar = count / total_count
            progressbar_sum = progressbar_sum + progressbar
            progress = round(progressbar_sum * 100)
            self.db_signal.emit(str(progress))
